package com.atguigu.gmall.activity.service.impl;

import com.atguigu.gmall.activity.component.SeckillLocalCache;
import com.atguigu.gmall.activity.mapper.SeckillMapper;
import com.atguigu.gmall.activity.service.SeckillService;
import com.atguigu.gmall.common.cache.GmallCache;
import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.common.result.ResultCodeEnum;
import com.atguigu.gmall.common.util.MD5;
import com.atguigu.gmall.model.activity.SeckillGoods;
import com.atguigu.gmall.model.list.Goods;
import com.atguigu.gmall.model.order.OrderInfo;
import com.atguigu.gmall.mq.MqConst;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import static org.bouncycastle.asn1.cmc.CMCStatus.success;

@Service
public class SeckillServiceImpl extends ServiceImpl<SeckillMapper, SeckillGoods> implements SeckillService {

    @Autowired
    SeckillMapper seckillMapper;

    @Autowired
    SeckillService seckillService;

    @Autowired
    StringRedisTemplate redisTemplate;

    @Autowired
    SeckillLocalCache seckillLocalCache;

    @Autowired
    RabbitTemplate rabbitTemplate;

    @Autowired
    RedissonClient redissonClient;

    @Autowired
    ThreadPoolExecutor executor;


    /**
     * seckill:goods:2022-1-18 [{},{}]
     * formatdate:当前日期
     * formattime:当前时间
     *
     * 切面？当前Service的代理对象,所以定时任务不能直接调，自己调自己，属于循环依赖
     *
     * @return
     */
    @GmallCache(enableBloom = false,cacheKeyExpr = RedisConst.SECKILL_GOODS + "#{#formatdate}",ttl = 60*60*24*1000L)
    @Override
    public List<SeckillGoods> getCurrentSeckillGoods() {
        List<SeckillGoods> goods = seckillMapper.getCurrentSeckillGoods();
        return goods;
    }

    /**
     * 生成秒杀码
     * @param skuId
     * @param userId
     * @return
     */
    @Override
    public String generateSeckillCode(Long skuId, String userId) {
        SeckillGoods goods = seckillLocalCache.getSeckillGoodsBySkuId(skuId);
        Date startTime = goods.getStartTime();
        long timeTime = startTime.getTime();
        long l = System.currentTimeMillis();
        if (timeTime <= l){
            //秒杀开始----生成秒杀码
            String encrypt = MD5.encrypt(skuId.toString() + "_" + userId);
            return encrypt;
        }
        return "error";
    }

    /**
     * 校验秒杀码
     * @param skuId
     * @param userId
     * @param code
     * @return
     */
    @Override
    public Boolean checkSeckillCode(Long skuId, String userId, String code) {
        String encrypt = MD5.encrypt(skuId.toString() + "_" + userId);
        if (encrypt.equals(code)){
            return true;
        }
        return false;
    }

    /**
     * 从本地缓存中拿所有秒杀商品
     * @return
     */
    @Override
    public List<SeckillGoods> getCurrentSeckillGoodsFromCache() {
        List<SeckillGoods> list = seckillLocalCache.todaySeckillGoods();
        return list;
    }

    /**
     * 根据skuId返回秒杀商品
     * @param skuId
     * @return
     */
    @Override
    public SeckillGoods getSeckillGoodsById(Long skuId) {
        SeckillGoods goods = seckillLocalCache.getSeckillGoodsBySkuId(skuId);
        return goods;
    }

    @Override
    public boolean seckillStockLocked(Long skuId, String skuIdStr, String userId) {
        RSemaphore semaphore = redissonClient.getSemaphore(RedisConst.SECKILL_STOCK_PREFIX + skuId.toString());

        //不阻塞方法
        boolean b = semaphore.tryAcquire();

        //顺便扣一下本地cache中的商品缓存----本地状态位的延迟更新
        SeckillGoods goods = seckillLocalCache.getSeckillGoodsBySkuId(skuId);
        if ((goods.getNum() - goods.getStockCount()) > 0){
            goods.setStockCount(goods.getStockCount() + 1);
        }

        if (!b){
            //秒杀完了,查看可用的信号还有多少
            int i = semaphore.availablePermits();
            if (i == 0){
                //给兄弟通知，当前商品秒杀结果，为啥不发消息队列，
                executor.submit(()->{
                    //提交10w  开发期间没关系，生产中多留50，通知商品很多的情况下，有些商品不能得到及时更新
                    //发出的消息，交给线程池，线程池队列很小只留最新的4个，
                    redisTemplate.convertAndSend(RedisConst.SECKILL_GOODS_OUT,skuId.toString());
                });
            }
        }

        return b;
    }

    /**
     * 防重检查
     * @param skuId
     * @param userId
     * @param skuIdStr
     * @return
     */
    @Override
    public boolean checkRepeat(Long skuId, String userId, String skuIdStr) {
        ValueOperations<String, String> operations = redisTemplate.opsForValue();
        SeckillGoods goods = seckillLocalCache.getSeckillGoodsBySkuId(skuId);
        long l = goods.getEndTime().getTime() - System.currentTimeMillis();
        //活动期间，这个商品只能秒一次
        Boolean aBoolean = operations.setIfAbsent(RedisConst.SECKILL_USER + skuIdStr, "", l, TimeUnit.MILLISECONDS);
        return aBoolean;
    }

    /**
     * 检查当前秒杀单进度
     * @param skuId
     * @param userId
     * @return
     */
    @Override
    public ResultCodeEnum checkOrderStatus(Long skuId, String userId) throws JsonProcessingException {
        //1、先看秒杀单是否存在redis中，并且，已经确认过秒杀单信息（填充了，收货地址信息）
        String orderJson = redisTemplate.opsForValue().get(RedisConst.SECKILL_USER + userId);
        if (StringUtils.isEmpty(orderJson)){
            //说明redis没有生成订单，可能状态是，正在排队，排队过了，但是运气不好没有抢到

            Boolean code = redisTemplate.hasKey(RedisConst.SECKILL_USER + MD5.encrypt(skuId.toString() + "_" + userId));
            if (code){
                //2、排了队  内存状态位验证通过了
                SeckillGoods goods = seckillLocalCache.getSeckillGoodsBySkuId(skuId);
                if ((goods.getNum() - goods.getStockCount())>0){
                    //有库存
                    return ResultCodeEnum.SECKILL_RUN;
                }
                return ResultCodeEnum.SECKILL_FINISH;
            }else{
                //1、如果没有排过队 内存状态位都没通过
                return ResultCodeEnum.SECKILL_FINISH;
            }

        }else {
            //说明下过单了
            OrderInfo orderInfo = new ObjectMapper().readValue(orderJson, OrderInfo.class);
            if (StringUtils.isEmpty(orderInfo.getConsignee())){
                //我们还没有确认订单
                return ResultCodeEnum.SECKILL_SUCCESS;
            }else {
                //我们已经确认了订单
                return ResultCodeEnum.SECKILL_ORDER_SUCCESS;
            }
        }

    }

    /**
     * 根据用户信息获取秒杀订单信息
     * @param userId
     * @return
     */
    @Override
    public OrderInfo getUserSeckillOrderInfo(String userId) throws JsonProcessingException {
        String strJson = redisTemplate.opsForValue().get(RedisConst.SECKILL_USER + userId);
        OrderInfo orderInfo = new ObjectMapper().readValue(strJson, OrderInfo.class);
        return orderInfo;
    }

    /**
     * 定时任务上架秒杀商品
     * 商品预热
     * cron = 秒 分 时 日 月 周
     */
    //@Scheduled(cron = "0/10 * * * * ?")
    @Scheduled(cron = "0 0 3 * * ?")
    public void scheduledUpSeckillGoods() throws JsonProcessingException {

        System.out.println("上架当天需要秒杀的商品,并盘点.....已经给消息队列发送消息");
        String message = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
        rabbitTemplate.convertAndSend(MqConst.SECKILL_EXCHANGE,MqConst.SECKILL_UPGOODS_RK,message);
    }

    /**
     * 清除前一天的所
     */
    @Override
    public void clearBeforeDatas() throws JsonProcessingException {
        //1、把seckill:旗下的清除
        //2、拿到昨天的上架的所有商品:seckill:goods:2022-08-01
        String s = LocalDate.now().minusDays(1).toString();
        String json = redisTemplate.opsForValue().get(RedisConst.SECKILL_GOODS + s);
        List<SeckillGoods> seckillGoods = new ObjectMapper().readValue(json, new TypeReference<List<SeckillGoods>>() {
        });

        //1、清理昨天的商品
        redisTemplate.delete(RedisConst.SECKILL_GOODS + s);
        //2、更新库存占位
        for (SeckillGoods good : seckillGoods) {
            Long skuId = good.getSkuId();
            //看当前商品信号量
            RSemaphore semaphore = redissonClient.getSemaphore(RedisConst.SECKILL_STOCK_PREFIX + skuId);
            int i = semaphore.availablePermits();
            good.setStockCount(good.getNum() - i);//代表卖了多少件

            seckillMapper.updateById(good);//按照id一更新

        }
    }


}
